package org.jl.demo.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jl.demo.authority.model.bo.Authority;
import org.jl.demo.authority.model.bo.Field;
import org.jl.demo.authority.model.bo.Function;
import org.jl.demo.authority.model.bo.Relation;
import org.jl.demo.generator.model.Class;
import org.jl.demo.generator.model.ClassFile;
import org.jl.demo.generator.model.Generator;
import org.jl.demo.generator.model.Method;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ClassFileUtil {
    public static final String CONTROLLER = "controller";
    public static final String SERVICE = "service";
    public static final String DAO = "dao";
    public static final String CRUD_DAO = "crud_dao";
    public static final String PAGING_DAO = "paging_dao";
    public static final String REPOSITORY = "repository";
    public static final String PO = "po";
    public static final String PK = "pk";
    public static final String PK_NAME = "PK";
    public static final String VO = "vo";
    public static final String PAGING = "Paging";

    private static final String SLASH = "/";
    private static final String DOT = ".";
    private static final String COMMA = ", ";
    private static final String A = "@";
    private static final String SPACE = " ";
    private static final String TAB = "    ";
    //    private static final String UNDERLINE = "_";
    private static final String TERMINATOR = ";\n";
    private static final String NEWLINE = "\n";
    private static final String OBJ_PREFIX = SPACE + "{";
    private static final String OBJ_SUFFIX = "}";
    private static final String PARAM_PREFIX = "(";
    private static final String PARAM_SUFFIX = ")";
    private static final String GENERIC_PREFIX = "<";
    private static final String GENERIC_SUFFIX = ">";
    private static final String PACKAGE = "package" + SPACE;
    private static final String IMPORT = "import" + SPACE;
    private static final String PRIVATE = TAB + "private" + SPACE;
    private static final String PUBLIC = TAB + "public" + SPACE;
    private static final String EXTENDS = SPACE + "extends" + SPACE;
    private static final String IMPLEMENTS = SPACE + "implements" + SPACE;
    private static final String CLASS = "public class" + SPACE;
    private static final String INTERFACE = "public interface" + SPACE;
    private static final String MODEL = "model";
    private static final String TABLE = "Table";
    private static final String REQUEST_MAPPING = "RequestMapping";
    private static final String MAPPING = "Mapping";
    private static final String FORM = "Form";
    private static final String PAGE = "Page";
    private static final String OPTIONAL = "Optional";
    private static final String DETAIL = "Detail";
    private static final String INDEX = "index";
    private static final String EDIT = "edit";
    private static final String DELETE = "delete";
    private static final String ID = "Id";
    private static final String A_ID = "@Id";
    private static final String A_COLUMN = TAB + "@Column";
    private static final String A_ID_CLASS = "@IdClass";
    private static final String A_INDEX = TAB + TAB + "@Index";
    private static final String A_RESOURCE = TAB + "@Resource";
    /**
     * @
     */
    private static final String XA = "xa0";
    /**
     * Group
     */
    private static final String X0 = "x0";
    /**
     * group
     */
    private static final String X1 = "x1";
    /**
     * Group group
     */
    private static final String X2 = "x2";
    /**
     * groupDetail
     */
    private static final String X3 = "x3";
    /**
     * Group groupDetail
     */
    private static final String X4 = "x4";
    /**
     * groups
     */
    private static final String X5 = "x5";
    /**
     * Page<Group> groups
     */
    private static final String X6 = "x6";

    private ClassFile controller = null;
    private ClassFile service = null;
    private ClassFile crudDao = null;
    private ClassFile pagingDao = null;
    private ClassFile po = null;
    private ClassFile pk = null;

    private final String tablePrefix;
    private final String packagePrefix;
    public final Map<String, Function> functionMap;
    public final Map<String, Relation> relationMap;

    public ClassFileUtil(Map<String, Object> config, Map<String, Object> data) {
        ObjectMapper mapper = new ObjectMapper();
        //下划线转小驼峰
        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
        if (config != null) {
            Generator generator = mapper.convertValue(config, Generator.class);
            Map<String, ClassFile> classFile = generator.getClassFile();
            this.controller = classFile.get(CONTROLLER);
            this.service = classFile.get(SERVICE);
            this.crudDao = classFile.get(CRUD_DAO);
            this.pagingDao = classFile.get(PAGING_DAO);
            this.po = classFile.get(PO);
            this.pk = classFile.get(PK);
        }

        Authority authority = mapper.convertValue(data, Authority.class);
        this.tablePrefix = authority.getTablePrefix();
        this.packagePrefix = authority.getPackagePrefix();
        this.functionMap = authority.getFunction();
        this.relationMap = authority.getRelation();
    }

    //公共方法

    //组装字符串

    public String getCamelCase(String functionName, boolean upperCase) {
        String result = Arrays.stream(functionName.split("_"))
                .filter(s -> !s.isEmpty())
                .map(StringUtils::capitalize)
                .collect(Collectors.joining());
        if (upperCase) {
            return result;
        }
        return Character.toLowerCase(result.charAt(0)) + result.substring(1);
    }

    public String getFilePath(String pathPrefix, String packageType) {
        String packagePath = getPackagePath(packageType);
        return pathPrefix + packagePath.replace(".", "/");
    }

    public String getTableName(String functionName) {
        return tablePrefix + functionName;
    }

    public String getModelPKStr(String functionName) {
        StringBuilder str = new StringBuilder();
        //package
        str.append(getPackagePathStr(PO));
        //import
        //是否导入PO的包，这里无需导入，functionName传空
        str.append(getImportStr(pk.getImports()));

        Class classObj = pk.getClassObj();
        Function function = getFunction(functionName, true);
        // comment
        str.append(getComment(function.getComment(), getCamelCase(PK, true), false));
        // annotation
        str.append(getClassAnnotation(classObj.getAnnotations()));
        //implements
        String implementsStr = getImplementsStr(classObj.getImplementsList());
        // class
        String className = getCamelCase(functionName, true);
        String classContent = getFieldStr(function.getField(), function.getNullable(), false, true);
        str.append(getAllClassContent(className + PK_NAME, null,
                null, implementsStr, classContent));
        return str.toString();
    }

    public String getModelStr(String functionName, boolean isRelation) {
        StringBuilder str = new StringBuilder();
        // package
        str.append(getPackagePathStr(PO));
        // import
        str.append(getImportStr(po.getImports()));

        Class classObj = po.getClassObj();
        Function function = getFunction(functionName, isRelation);
        String className = getCamelCase(functionName, true);
        // comment
        str.append(getComment(function.getComment(), getCamelCase(PO, true), false));
        // annotation
        List<String> indexs = function.getIndex();
        str.append(getClassAnnotation(classObj.getAnnotations(), getTableName(functionName), indexs));
        if (isRelation) {
            str.append(getIdClassAnnotation(className));
        }
        // class
        String classContent = getFieldStr(function.getField(), function.getNullable(), isRelation, false);
        str.append(getAllClassContent(className, null, null, null, classContent));
        return str.toString();
    }

    public String getDaoStr(String functionName, boolean isRelation, boolean isPaging) {
        Function function = getFunction(functionName, isRelation);
        String className = getCamelCase(functionName, true);
        StringBuilder str = new StringBuilder();
        // package
        str.append(getPackagePathStr(DAO));
        // import
        List<String> imports;
        if (isPaging) {
            imports = pagingDao.getImports();
        } else {
            imports = crudDao.getImports();
        }
        str.append(getImportStr(imports, functionName, null, null));

        Class classObj;
        if (isPaging) {
            classObj = pagingDao.getClassObj();
        } else {
            classObj = crudDao.getClassObj();
        }
        String classContent = function.getComment();
        String packageTypeName = getCamelCase(REPOSITORY, true);
        if (isPaging) {
            packageTypeName = PAGING + packageTypeName;
        }
        classContent += packageTypeName;

        // comment
        str.append(getComment(classContent, null, false));
        // annotation
        str.append(getClassAnnotation(classObj.getAnnotations()));
        // class
        str.append(getAllClassContent(className, packageTypeName, classObj.getExtendsStr(), null, null));
        return str.toString();
    }

    private void setDeclarationStr(StringBuilder context, String functionName, List<String> declarations) {
        // newline
        context.append(NEWLINE);
        String className = getCamelCase(functionName, true);
        for (String declaration : declarations) {
            //annotation
            context.append(A_RESOURCE).append(NEWLINE);
            //declaration
            context.append(PRIVATE).append(className).append(declaration).append(SPACE);
            context.append(functionName).append(declaration).append(TERMINATOR);
        }
    }

    private String logicStrReplace(String logic, String functionName) {
        String className = getCamelCase(functionName, true);
        return logic
                .replace(XA, A)
                .replace(X0, className)
                .replace(X1, functionName)
                .replace(X2, className + SPACE + functionName)
                .replace(X3, functionName + DETAIL)
                .replace(X4, className + SPACE + functionName + DETAIL)
                .replace(X5, functionName + "s")
                .replace(X6, PAGE + GENERIC_PREFIX + className + GENERIC_SUFFIX + SPACE + functionName + "s");
    }

    private String getMethodAnnotation(List<String> annotations, String methodName) {
        StringBuilder str = new StringBuilder();
        String path;
        for (String annotation : annotations) {
            str.append(TAB).append(A).append(annotation);
            if (annotation.endsWith(MAPPING)) {
                path = SLASH;
                if (!INDEX.equals(methodName)) {
                    path += methodName.replace(FORM, "");
                    if (List.of(EDIT, DELETE).contains(methodName)) {
                        path += "/{id}";
                    }
                }
                str.append("(name = \"").append(path).append("\")");
            }
            str.append(NEWLINE);
        }
        return str.toString();
    }

    private void setMethodStr(StringBuilder context, String functionName, String comment, Map<String, Method> methods) {
        // newline
        context.append(NEWLINE);
        for (Map.Entry<String, Method> entry : methods.entrySet()) {
            Method m = entry.getValue();
            String methodName = getCamelCase(entry.getKey(), false);
            // comment
            context.append(getComment(comment, m.getComment(), true));
            // annotation
            if (ObjectUtils.isNotEmpty(m.getAnnotations())) {
                context.append(getMethodAnnotation(m.getAnnotations(), methodName));
            }
            context.append(PUBLIC).append(getReturnTypeStr(m.getReturnType(), functionName));
            context.append(SPACE).append(methodName).append(PARAM_PREFIX);
            if (ObjectUtils.isNotEmpty(m.getParameters())) {
                StringBuilder paramStr = new StringBuilder();
                for (String param : m.getParameters()) {
                    if (!paramStr.isEmpty()) {
                        paramStr.append(COMMA);
                    }
                    paramStr.append(logicStrReplace(param, functionName));
                }
                context.append(paramStr);
            }
            context.append(PARAM_SUFFIX).append(OBJ_PREFIX).append(NEWLINE);
            if (ObjectUtils.isNotEmpty(m.getLogics())) {
                StringBuilder logicStr = new StringBuilder();
                for (String logic : m.getLogics()) {
                    logicStr.append(TAB).append(TAB).append(logicStrReplace(logic, functionName)).append(NEWLINE);
                }
                context.append(logicStr);
            }
            context.append(TAB).append(OBJ_SUFFIX).append(NEWLINE);
        }
    }

    private String getReturnTypeStr(String returnType, String functionName) {
        String className = getCamelCase(functionName, true);
        String genericStr = "";
        if (List.of(PAGE, OPTIONAL).contains(returnType)) {
            genericStr = GENERIC_PREFIX + className + GENERIC_SUFFIX;
        }
        return returnType + genericStr;
    }

    public String getServiceStr(String functionName) {
        Function function = getFunction(functionName, false);
        String className = getCamelCase(functionName, true);
        StringBuilder str = new StringBuilder();
        // package
        str.append(getPackagePathStr(SERVICE));
        // import
        Class classObj = service.getClassObj();
        List<String> imports = service.getImports();
        str.append(getImportStr(imports, functionName, classObj.getDeclarations(), DAO));

        String classContent = function.getComment();
        String packageTypeName = getCamelCase(SERVICE, true);
        classContent += packageTypeName;

        // comment
        str.append(getComment(classContent, null, false));
        // annotation
        str.append(getClassAnnotation(classObj.getAnnotations()));
        // classContext
        StringBuilder context = new StringBuilder();
        setDeclarationStr(context, functionName, classObj.getDeclarations());
        setMethodStr(context, functionName, function.getComment(), classObj.getMethodObj());
        // class
        str.append(getAllClassContent(className, packageTypeName, classObj.getExtendsStr(), null, context.toString()));
        return str.toString();
    }

    public String getControllerStr(String functionName) {
        Function function = getFunction(functionName, false);
        String className = getCamelCase(functionName, true);
        StringBuilder str = new StringBuilder();
        // package
        str.append(getPackagePathStr(CONTROLLER));
        // import
        Class classObj = controller.getClassObj();
        List<String> imports = controller.getImports();
        str.append(getImportStr(imports, functionName, classObj.getDeclarations(), SERVICE));

        String classContent = function.getComment();
        String packageTypeName = getCamelCase(CONTROLLER, true);
        classContent += packageTypeName;

        // comment
        str.append(getComment(classContent, null, false));
        // annotation
        str.append(getClassAnnotation(classObj.getAnnotations(), functionName));
        // classContext
        StringBuilder context = new StringBuilder();
        setDeclarationStr(context, functionName, classObj.getDeclarations());
        setMethodStr(context, functionName, function.getComment(), classObj.getMethodObj());
        // class
        str.append(getAllClassContent(className, packageTypeName, classObj.getExtendsStr(), null, context.toString()));
        return str.toString();
    }

    //获取对象


    //私有方法

    //工具方法

    //组装字符串

    private String getPackagePath(String packageType) {
        String packagePath = packagePrefix;
        if (PO.equals(packageType) || VO.equals(packageType)) {
            packagePath += DOT + MODEL;
        }
        return packagePath + DOT + packageType;
    }

    private String getPackagePathStr(String packageType) {
        return PACKAGE + getPackagePath(packageType) + TERMINATOR + NEWLINE;
    }

    private String getPoImportStr(String functionName) {
        return IMPORT + getPackagePath(PO) + DOT + getCamelCase(functionName, true) + TERMINATOR;
    }

    private String getImplementsStr(List<String> implementsList) {
        StringBuilder str = new StringBuilder();
        for (String imp : implementsList) {
            if (!str.isEmpty()) {
                str.append(COMMA);
            }
            str.append(imp);
        }
        return str.toString();
    }

    private String getImportStr(List<String> imports) {
        return getImportStr(imports, null, null, null);
    }

    private String getImportStr(List<String> imports, String functionName, List<String> otherImports, String functionType) {
        StringBuilder str = new StringBuilder();
        if (ObjectUtils.isNotEmpty(imports)) {
            if (StringUtils.isNotEmpty(functionName)) {
                str.append(getPoImportStr(functionName));
            }
            if (ObjectUtils.isNotEmpty(otherImports)) {
                for (String imp : otherImports) {
                    str.append(IMPORT).append(packagePrefix).append(DOT).append(functionType);
                    str.append(DOT).append(getCamelCase(functionName, true)).append(imp).append(TERMINATOR);
                }
            }
            for (String imp : imports) {
                str.append(IMPORT).append(imp).append(TERMINATOR);
            }
            str.append(NEWLINE);
        }
        return str.toString();
    }

    private String getComment(String comment, String commentSuffix, boolean isMethod) {
        String tab = isMethod ? TAB : "";
        return tab + "/**" + NEWLINE
                + tab + " * " + comment + StringUtils.defaultString(commentSuffix) + NEWLINE
                + tab + " */" + NEWLINE;
    }

    private String getAnnotation(String annotation) {
        return TAB + annotation + NEWLINE;
    }

    private String getClassAnnotation(List<String> annotations) {
        return getClassAnnotation(annotations, null, null);
    }

    private String getClassAnnotation(List<String> annotations, String functionName) {
        return getClassAnnotation(annotations, functionName, null);
    }

    private String getClassAnnotation(List<String> annotations, String tableName, List<String> indexs) {
        StringBuilder str = new StringBuilder();
        for (String annotation : annotations) {
            str.append(A).append(annotation);
            if (TABLE.equals(annotation)) {
                str.append("(name = \"").append(tableName).append("\"");
                if (ObjectUtils.isNotEmpty(indexs)) {
                    str.append(", indexes = {").append(NEWLINE);
                    int i = 0;
                    for (String index : indexs) {
                        if (i == 1) {
                            str.append(",").append(NEWLINE);
                        }
                        str.append(A_INDEX).append("(name = \"idx_").append(index)
                                .append("\", columnList = \"").append(index).append("\")");
                        i = 1;
                    }
                    str.append(NEWLINE).append("}");
                }
                str.append(")");
            } else if (REQUEST_MAPPING.equals(annotation)) {
                str.append("(name = \"").append(SLASH).append(tableName).append("\")");
            }
            str.append(NEWLINE);
        }
        return str.toString();
    }

    private String getIdClassAnnotation(String className) {
        return A_ID_CLASS + "(" + className + PK_NAME + ".class)" + NEWLINE;
    }

    private String getColumnAnnotation(String name, Boolean nullable, Boolean unique, Integer length) {
        String nullableStr = "";
        if (Boolean.FALSE.equals(nullable)) {
            nullableStr = ", nullable = false";
        }
        String uniqueStr = "";
        if (Boolean.TRUE.equals(unique)) {
            uniqueStr = ", unique = true";
        }
        String lengthStr = "";
        if (ObjectUtils.isNotEmpty(length)) {
            lengthStr = ", length = " + length;
        }
        return A_COLUMN + "(name = \"" + name + "\"" + nullableStr + uniqueStr + lengthStr + ")";
    }

    /**
     * tinyint，integer，bigint，varchar，decimal，datetime
     */
    private String getFieldType(String columnType, Integer length) {
        return switch (columnType) {
            case "tinyint" -> {
                if (Integer.valueOf(1).equals(length)) {
                    yield "Boolean";
                }
                yield "Byte";
            }
            case "integer" -> "Integer";
            case "bigint" -> "Long";
            case "decimal" -> "BigDecimal";
            case "datetime" -> "LocalDateTime";
            default -> "String";
        };
    }

    private String getFieldStr(Map<String, Field> fieldMap, Boolean nullable, boolean isRelation, boolean isPk) {
        StringBuilder str = new StringBuilder();
        if (ObjectUtils.isNotEmpty(fieldMap)) {
            for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
                Field field = entry.getValue();
                if (ObjectUtils.isEmpty(nullable)) {
                    nullable = field.getNullable();
                }
                //comment
                str.append(getComment(field.getComment(), null, true));
                if (!isPk) {
                    //@Id
                    if (isRelation || ID.toLowerCase().equals(entry.getKey())) {
                        str.append(getAnnotation(A_ID));
                    }
                    //@Column
                    str.append(getColumnAnnotation(entry.getKey(), nullable, field.getPk(), field.getLength())).append(NEWLINE);
                }
                //declaration
                str.append(PRIVATE).append(getFieldType(field.getType(), field.getLength()))
                        .append(SPACE).append(getCamelCase(entry.getKey(), false)).append(TERMINATOR);
            }
        }
        return str.toString();
    }

    private String getAllClassContent(String className, String packageTypeName,
                                      String extendsStr, String implementsStr, String classContent) {
        String extendsString = "";
        if (StringUtils.isNotEmpty(extendsStr)) {
            extendsString += EXTENDS + extendsStr + "<" + className + ", Integer>";
        }
        String implementsString = "";
        if (StringUtils.isNotEmpty(implementsStr)) {
            implementsString += IMPLEMENTS + implementsStr;
        }
        packageTypeName = StringUtils.defaultString(packageTypeName);
        String classType = CLASS;
        if (packageTypeName.toLowerCase().contains(REPOSITORY)) {
            classType = INTERFACE;
        }
        return classType + className + packageTypeName
                + extendsString
                + implementsString
                + OBJ_PREFIX + NEWLINE
                + StringUtils.defaultString(classContent)
                + OBJ_SUFFIX;
    }

    //获取对象

    private Function getFunction(String functionName, boolean isRelation) {
        if (isRelation) {
            return relationMap.get(functionName);
        }
        return functionMap.get(functionName);
    }

}
